const { KeyObject } = require('crypto')
const { response } = require('express')
const http = require('http')
var net = require('net')
var ACTIONS = require('./actions')
var { Config } = require('./config')
const { Log } = require('./logger')

var UpDataCountHold = new Map()

var DownDataCountHold = new Map()

class SlaveClient {

    constructor(serverHost, serverPort, serverDataPort, nodeName) {
        this.serverHost = serverHost
        this.serverPort = serverPort
        this.serverDataPort = serverDataPort
        this.nodeName = nodeName
        this.sSocket = null

        this.dataSocketHold = new Map()
        this.localSocketHold = new Map()

        this.printSpeedCount = 1

    }

    getSSocket() {

        if (process.env.HTTP_PROXY_SERVER) {

            const options = {
                port: process.env.HTTP_PROXY_SERVER_PORT,
                host: process.env.HTTP_PROXY_SERVER,
                method: 'CONNECT',
                path: this.serverHost + ":" + this.serverPort
            };

            // const keepAliveAgent = new http.Agent({ keepAlive: true });
            // options.agent = keepAliveAgent;

            const req = http.request(options);
            req.end()
            req.on('connect', (resp, socket, head) => {
                Log.info("request connected by http Proxy")
                Log.info("DATA  connect resp.heads", resp.method)
                this.sSocket = socket
            })
            req.on('error', (err)=>{
                Log.error("http Proxy error", err);
                req.destroy()
                setTimeout(() => {
                    Log.info("try to reconnect to server, by http proxy")
                    this.getSSocket()
                }, 3500)
            })

        } else {

            this.sSocket = net.connect(this.serverPort, this.serverHost, () => {
                Log.info("request connected directly")
            })

        }

        var lamfun = () => {

            if(this.sSocket==null){
                Log.info("this sSocket is null")
                setTimeout(lamfun, 3000)
                return 
            }

            Log.info("nodeClient: connected")
            Log.info('local = %s:%s', this.sSocket.localAddress, this.sSocket.localPort)
            Log.info('remote= %s:%s', this.sSocket.remoteAddress, this.sSocket.remotePort);
            this.sSocket.setDefaultEncoding('utf-8')
            this.sSocket.setEncoding('utf-8')
            this.sSocket.setTimeout(5000)

            var reg = new ACTIONS.REG(this.nodeName)
            this.send(reg)

            this.sSocket.on('data', (data) => {
                var str = data.toString()
                Log.info(">> %s, s%", this.nodeName, str)
                str.split("\r\n").filter((it) => { return !(it == '') }).forEach((item) => {

                    var act = JSON.parse(item)
                    switch (act.Action) {
                        case ACTIONS.REQLINK_STR:
                            var id = act.id
                            var port = act.port
                            this.linkPort(id, port)
                            break;
                        case ACTIONS.RESPCONFIRM_STR:
                            var id = act.id
                            this.setOnData(id)
                            break;
                        default:
                            ;
                    }

                })
            })

            this.sSocket.on('error', (err) => {
                Log.error("nodeclient error", err)
                if (this.sSocket) {
                    this.sSocket.destroy()
                }
                this.sSocket = null
                setTimeout(() => {
                    Log.info("try to reconnect to server")
                    this.getSSocket()
                }, 3500)

            })
        }

        
        lamfun();




    }


    start() {
        this.getSSocket()
        setInterval(() => {
            var act = new ACTIONS.HBEAT(this.nodeName)
            this.send(act)
        }, 20000)


        setInterval(() => {
            this.speedInteval(5)
        }, 5000)
    }

    send(action) {
        if (this.sSocket) {
            try {
                Log.info("send [%s]", JSON.stringify(action))
                this.sSocket.write(JSON.stringify(action) + "\r\n")
                return true
            } catch (error) {
                Log.info("nodesocket error", error)
                return false
            }
        }

    }

    linkPort(id, port) {

        let datasocket = null

        if (process.env.HTTP_PROXY_SERVER) {

            const options = {
                port: process.env.HTTP_PROXY_SERVER_PORT,
                host: process.env.HTTP_PROXY_SERVER,
                method: 'CONNECT',
                path: this.serverHost + ":" + this.serverDataPort
            };

            // const keepAliveAgent = new http.Agent({ keepAlive: true });
            // options.agent = keepAliveAgent;

            const req = http.request(options);
            req.end()
            req.on('connect', (resp, socket, head) => {
                Log.info("request connected by http Proxy, DATA server")
                datasocket = socket
            })

        } else {

            datasocket = net.connect(this.serverDataPort, this.serverHost, () => {
                Log.info("request connected directly, DATA SERVER");
            })

        }


        setTimeout(() => {

            Log.info('nodeclient connect dataserver')
            Log.info("\t local %s, %s", datasocket.localAddress, datasocket.localPort)
            Log.info("\t remote %s, %s", datasocket.remoteAddress, datasocket.remotePort)

            this.dataSocketHold.set(id, datasocket)
            datasocket.connection_id = id
            var act = new ACTIONS.RESPLINK(id, this.nodeName, port)
            datasocket.write(JSON.stringify(act) + "\r\n")

            datasocket.on('close', (has_err) => {
                Log.info("datasocket on close %s", id)
                this.remove(id)
            })

            datasocket.on('error', (err) => {
                Log.info("datasocket on err ", err)
                this.remove(id)
            })

            var localsocket = net.connect(port, () => {

                Log.info('nodeclient connect local port %s', port)
                Log.info("\t local %s, %s", localsocket.localAddress, localsocket.localPort)
                Log.info("\t remote %s, %s", localsocket.remoteAddress, localsocket.remotePort)

                localsocket.connection_id = id
                this.localSocketHold.set(id, localsocket)

            })

            localsocket.on('close', (has_err) => {
                Log.info("localsocket on close %s", id)
                this.remove(id)
            })

            localsocket.on('error', (err) => {
                Log.info("localsocket on err %s", id)
                this.remove(id)
            })
        }, 2000)
    }

    remove(id) {
        if (this.dataSocketHold.has(id)) {
            this.dataSocketHold.get(id).destroy()
        }
        if (this.localSocketHold.has(id)) {
            this.localSocketHold.get(id).destroy()
        }

        this.dataSocketHold.delete(id)
        this.localSocketHold.delete(id)

        UpDataCountHold.delete(id)
        DownDataCountHold.delete(id)
    }

    setOnData(id) {
        var ls = this.localSocketHold.get(id)
        var ds = this.dataSocketHold.get(id)
        if (ls && ds && ls.on !== undefined && ds.on !== undefined) {


            ls.on('data', (data) => {
                this.count(UpDataCountHold, id, data.length)
                ds.write(data)
            })

            ds.on('data', (data) => {
                this.count(DownDataCountHold, id, data.length)
                ls.write(data)
            })


        } else {
            Log.error("datasocket setOnData error %s , localsocket or datasocket is null", id)
            if (ls) {
                ls.destroy()
            }

            if (ds) {
                ds.destroy()
            }
        }
    }


    count(map, id, num) {
        if (!map.has(id)) {
            map.set(id, { total: 0, total_his: 0, speed: 0 })
        }
        var cinf = map.get(id)
        cinf.total = cinf.total + num
    }

    speedInteval(time) {

        this.printSpeedCount = this.printSpeedCount - 1;

        UpDataCountHold.forEach((value, key) => {
            var speed = (value.total - value.total_his) * 8 / (time * 1000)
            value.speed = speed.toFixed(3);
            value.total_his = value.total
            if (this.printSpeedCount == 0) {
                Log.info("Up\t %s\t speed: %s Kbps", key, value.speed)
            }
        })

        DownDataCountHold.forEach((value, key) => {
            var speed = (value.total - value.total_his) * 8 / (time * 1000)
            value.speed = speed.toFixed(3);
            value.total_his = value.total
            if (this.printSpeedCount == 0) {
                Log.info("Down\t %s\t speed: %s Kbps", key, value.speed)
            }
        })

        if (this.printSpeedCount == 0) {
            this.printSpeedCount = 3
        }
    }

}



var nodeclient = new SlaveClient(Config.serverHost, Config.serverPort, Config.serverDataPort, Config.slaveName)

nodeclient.start()
